<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2020-9-28 10:55:00
// +----------------------------------------------------------------------

namespace buwang\util;

use buwang\exception\WxException;

/**
 * 微信签名认证
 */
class Sign
{

    /**
     * httpsRequest  https请求（支持GET和POST）
     * @param $url
     * @param null $data
     * @return mixed
     */
    public static function httpsRequest($url, $data = '', array $headers = [], $cert = [], $timeout = 30)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        //设置超时
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
        if (!empty($cert)) {
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); //严格校验
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            list($sslCertPath, $sslKeyPath) = $cert;
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($curl, CURLOPT_SSLKEY, $sslKeyPath);
        } else {
            if (substr($url, 0, 5) == 'https') {
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名
            }
        }
        if (!empty($headers)) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
        curl_setopt($curl, CURLOPT_HEADER, true);    // 是否需要响应 header
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);    // 获得响应结果里的：头大小
        $response_body = substr($output, $header_size);
        curl_close($curl);
        return self::fromXml($response_body);
    }

    /**
     * decryptCiphertext  AEAD_AES_256_GCM 解密加密后的证书内容得到平台证书的明文
     * sodium_crypto_aead_aes256gcm_decrypt >=7.2版本
     * 去php.ini里面开启下libsodium扩展就可以，之前版本需要安装libsodium扩展，具体查看php.net
     * （ps.使用这个函数对扩展的版本也有要求哦，扩展版本 >=1.08）
     * @param $data
     * @param $key
     * @return string
     */
    public static function decryptCiphertext(array $data, string $key)
    {
        $check_sodium_mod = extension_loaded('sodium');
        if ($check_sodium_mod === false) {
            echo '没有安装sodium模块';
            die;
        }
        $check_aes256gcm = sodium_crypto_aead_aes256gcm_is_available();
        if ($check_aes256gcm === false) {
            echo '当前不支持aes256gcm';
            die;
        }
        $encryptCertificate = $data['encrypt_certificate'];                      //证书内容
        $ciphertext = $encryptCertificate['ciphertext'];                 //加密后的证书内容;
        $associated_data = $encryptCertificate['associated_data'];            //加密证书的随机串,固定值:certificate;
        $nonce = $encryptCertificate['nonce'];                      //加密证书的随机串,加密证书的随机串
        return sodium_crypto_aead_aes256gcm_decrypt(base64_decode($ciphertext), $associated_data, $nonce, $key);
    }

    /**
     * publicKeyEncrypt 对身份证等敏感信息加密
     * @param string $string
     * @return string
     * @throws WxException
     */
    public static function publicKeyEncrypt(string $string, string $publicKey)
    {
        $crypted = '';
        if ($publicKey) {
            $publicKeyResource = openssl_get_publickey($publicKey);
            $f = openssl_public_encrypt($string, $crypted, $publicKeyResource, OPENSSL_PKCS1_PADDING);
            openssl_free_key($publicKeyResource);
            if ($f) {
                return base64_encode($crypted);
            }
        }
        throw new WxException(20002);
    }

    /**
     * MakeSign 生成签名
     * @param $data
     * @param string $signType
     * @return string
     */
    public static function makeSign(array $data, $diy_key, $signType = 'HMAC-SHA256')
    {
        //签名步骤一：按字典序排序参数
        ksort($data);
        $string = self::toUrlParams($data);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $diy_key;
        //签名步骤三：MD5加密或者HMAC-SHA256
        if ($signType == 'md5') {
            //如果签名小于等于32个,则使用md5验证
            $string = md5($string);
        } else {
            //是用sha256校验
            $string = hash_hmac("sha256", $string, $diy_key);
        }
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * ToUrlParams 格式化参数格式化成url参数
     * @param $data
     * @return string
     */
    public static function toUrlParams(array $data)
    {
        $buff = "";
        foreach ($data as $k => $v) {
            if ($k != "sign" && $v !== "" && $v !== "null" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     **/
    public static function toXml($data)
    {
        if (!is_array($data) || count($data) <= 0) {
            throw new WxException(30001);
        }
        $xml = "<xml>";
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
    public static function fromXml($xml)
    {
        if (!$xml) {
            throw new WxException(30000);
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xml_parser = xml_parser_create();
        if (!xml_parse($xml_parser, $xml, true)) {
            xml_parser_free($xml_parser);
            throw new WxException(30000);
        } else {
            $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        }
        return $arr;
    }

    /**
     * getMillisecond 获取毫秒级别的时间戳
     * @return float
     */
    public static function getMillisecond()
    {
        list($msec, $sec) = explode(' ', microtime());
        $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
        return $msectime;
    }

    /**
     * getRandChar 获取随机字符串
     * @param int $length
     * @return null|string
     */
    public static function getRandChar($length = 32)
    {
        $str = NULL;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $newStr = str_shuffle($strPol);
        $max = strlen($strPol) - 1;
        for ($i = 0; $i < $length; $i++) {
            $str .= $newStr[mt_rand(0, $max)];
        }
        return $str;
    }
}
